/*
 * File: dec_calc.c
 * Author:  Yin Lijun <yinlijun2004@gmail.com>
 * Brief:   demicial calculator inplement.
 *
 * Copyright (c) 2011 - 2012   Yin Lijun <yinlijun2004@gmail.com>
 *
 * Licensed under the Academic Free License version 2.1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * History:
 * ================================================================
 * 2012-03-12 Yin Lijun <yinlijun2004@gmail.com> created
 *
 */
#include "calculator.h"

#define DEC_OPER_EQU '='
#define DEC_OPER_MNS '-'
#define DEC_OPER_PLU '+'
#define DEC_OPER_MUL '*'
#define DEC_OPER_DIV '/'
#define DEC_OPER_LBR '('
#define DEC_OPER_RBR ')'

static void dec_dest_func(void* data)
{
	free(data);
	return;
}

static Ret valid_dec_operator(char c)
{
	switch(c)
	{
	case DEC_OPER_RBR:
	case DEC_OPER_MUL:
	case DEC_OPER_DIV:
	case DEC_OPER_PLU:
	case DEC_OPER_MNS:
	case DEC_OPER_LBR:
	case DEC_OPER_EQU:
		return RET_TURE;
	default:
		return RET_FALSE;
	}
}

int dec_need_bt_func(void* data)
{
	char op = *(char *)data;
	switch(op)
	{
	case DEC_OPER_RBR:
		return 1;
	
	default:
		return 0;
	}
}

int dec_oper_prior_cmp(void* left, void* right)
{
	char l = *(char *)left;
	char r = *(char *)right;
	switch(l)
	{
	/* If last operation is ')', return ture anywary*/
	case DEC_OPER_MUL:
	case DEC_OPER_DIV:
	case DEC_OPER_EQU:
		if(r == DEC_OPER_LBR)
		{
			return 0;
		}
		return 1;

	case DEC_OPER_PLU:
	case DEC_OPER_MNS:
		if(r == DEC_OPER_PLU 
			|| r == DEC_OPER_MNS 
			|| r == DEC_OPER_RBR)
		{
			return 1;
		}
		return 0;
	
	case DEC_OPER_LBR:
		if(r == DEC_OPER_RBR)
		{
			return 1;
		}
		return 0;
	
	case DEC_OPER_RBR:
	default:
		return 0;
	}
}

int dec_get_oprs_num(void* oper)
{
	char op = *(char *)oper;
	switch(op)
	{
	case DEC_OPER_EQU:
	case DEC_OPER_RBR:
		return 1;
		
	case DEC_OPER_MNS:
	case DEC_OPER_PLU:
	case DEC_OPER_MUL:
	case DEC_OPER_DIV:
		return 2;
	
	case DEC_OPER_LBR:
		return 0;

	default:
		return 0;
	}
}

void* dec_calc_func(void* oper, void** data, int size)
{
	char op = *(char *)oper;
	double* result = (double *)malloc(sizeof(double));
	
	switch(op)
	{
	case DEC_OPER_EQU:
		*result = *(double *)data[0];
		return (void *)result;
	
	case DEC_OPER_MNS:
		if(size == 2)
		{
			double r = *((double *)data[0]);
			double l = *((double *)data[1]);
			*result = l - r;
			return (void *)result;
		}
		printf("Oops! something wrong when minus!");
		return NULL;

	case DEC_OPER_PLU:
		if(size == 2)
		{
			double r = *((double *)data[0]);
			double l = *((double *)data[1]);
			*result = l + r;
			return (void *)result;
		}
		printf("Oops! something wrong when plus!");
		return NULL;

	case DEC_OPER_DIV:
		if(size == 2)
		{	
			double r = *((double *)data[0]);
			double l = *((double *)data[1]);
			*result = l / r;
			return (void *)result;
		}
		printf("Oops! something wrong when divide!");
		return NULL;
	
	case DEC_OPER_MUL:
		if(size == 2)
		{	
			double r = *((double *)data[0]);
			double l = *((double *)data[1]);
			*result = l * r;
			return (void *)result;
		}
		printf("Oops! something wrong when multiply!");
		return NULL;
	
	case DEC_OPER_LBR:
		if(size == 1)
		{
			*result = *((double *)data[0]);	
			return (void *)result;
		}
		printf("Oops! something wrong when left brackets!");
		return NULL;
	
	case DEC_OPER_RBR:
		if(size == 1)
		{
			*result = *((double *)data[0]);	
			return (void *)result;
		}
		printf("Oops! something wrong when right brackets!");
		return NULL;

	default:
		printf("Oops! something wrong when calculation!");
		return NULL;	
	}
}

static Ret is_number(char op)
{
	if (op >= '0' && op <= '9')
	{
		return RET_TURE;
	}
	
	return RET_FALSE;
}

static Ret to_result(char* s)
{
	if(strlen(s) == 0 || strcmp(s, "=") == 0)
	{
		return RET_TURE;
	}

	return RET_FALSE;
}
static void int_visit_func(void* ctx, void* data)
{
	printf("%d:%lf ", *(int *)ctx, *(double *)data);
	*(int *)ctx += 1;
}

static void op_visit_func(void* ctx, void* data)
{
	printf("%d:%c ", *(int *)ctx, *(char *)data);
	*(int *)ctx += 1;
}

static double calc_dec_exp(Calculator* calc, char* exp)
{
	char* next_ptr = exp;
	double ret = 0.00;
	
	while(1)
	{
		char c = exp[0];
		
		if (is_number(c) == RET_TURE)
		{
			double num = strtod(exp, &next_ptr);
			exp = next_ptr;
			void* stored = (void *)malloc(sizeof(double));
			memcpy(stored, &num, sizeof(double));
			calc_on_data(calc, stored);
			int i = 0;
			calc_foreach_data(calc, int_visit_func, &i);
			printf("\n");
			continue;
		}

		if (valid_dec_operator(c) == RET_TURE)
		{
			void* stored = (void *)malloc(sizeof(char));
			memcpy(stored, &c, sizeof(char));
			calc_on_operator(calc, stored);
			int i = 0;
			calc_foreach_op(calc, op_visit_func, &i);
			printf("\n");
			exp++;
			continue;
		}

		if (to_result(exp) == RET_TURE)
		{
			//calc result
			calc_on_end(calc);
			//get result!
			double* r = calc_get_result(calc);
			ret = *r;
			dec_dest_func(r);
			r = NULL;
			break;
		}

		printf("Oops! somthing wrong when parse");
		break;
	}

	return ret;
}

Ret calc(char* exp, double* result)
{
	CalcIface ci = {
		.data_dest_func 	= dec_dest_func,
		.op_dest_func 		= dec_dest_func,
		.op_prior_cmp_func 	= dec_oper_prior_cmp,
		.need_bt_func 		= dec_need_bt_func,
		.calc_func 			= dec_calc_func,
		.get_oprs_num_func	= dec_get_oprs_num	
	};

	Calculator* calc = calc_create(&ci);
	return_if_fail_val(calc != NULL, RET_OVERFLOW);

	*result = calc_dec_exp(calc, exp);
	calc_destroy(calc);

	return RET_OK;
}

#ifdef __CALCULATOR_TEST__
#include <stdio.h>
#include <assert.h>
int main(int argc, char** argv)
{
	char* expression = "((1/(4-2))*2+(2-3)+2*4)+(4+2)/2";
	double result = 0.0;
	char* exp = strdup(expression);
	calc(exp, &result);
	printf("%s=%lf\n", exp, result);
	assert(result == 11.00);
	free(exp);
		
	return 0;
}
#endif
